package com.ruoyi.framework.shiro.web.filter.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.jwt.JwtException;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.framework.shiro.token.JwtToken;
import com.ruoyi.framework.util.JwtUtil;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.service.ISysUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

/**
 * jwt验证器
 * 
 * @author phil
 */
public class JwtValidateFilter extends AccessControlFilter{
	private final static ObjectMapper objectMapper = new ObjectMapper();

	private static final Logger log = LoggerFactory.getLogger(JwtValidateFilter.class);

	/**
	 * 是否开启jwt验证
	 */
	private boolean jwtEnabled = true;

	/**
	 * 首页地址
	 */
	private String loginUrl;

	/**
	 * 不拦截的请求地址，多地址使用逗号分隔
	 */
	private String excludes;

	private UrlPathHelper pathHelper = new UrlPathHelper();

	private PathMatcher pathMatcher = new AntPathMatcher();

	public boolean getJwtEnabled(){
		return jwtEnabled;
	}

	public void setJwtEnabled(boolean jwtFlag){
		this.jwtEnabled = jwtFlag;
	}

	public String getLoginUrl(){
		return loginUrl;
	}

	public void setLoginUrl(String loginUrl){
		this.loginUrl = loginUrl;
	}

	public String getExcludes(){
		return excludes;
	}

	public void setExcludes(String excludes){
		this.excludes = excludes;
	}

	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception{
		// 没有开启白名单，直接放行
		if(!jwtEnabled){
			return true;
		}

		// 白名单校验，白名单内放行
		String path = pathHelper.getLookupPathForRequest((HttpServletRequest)request);
		if(path == null || "".equals(path)){
			throw new RuntimeException("sdf");
		}
		if(excludes != null && !"".equals(excludes)){
			String[] exceludesArray = excludes.split(",");
			if(exceludesArray != null && exceludesArray.length > 0){
				for(String pattern : exceludesArray){
					if(pathMatcher.match(pattern, path)){
						return true;
					}
				}
			}
		}

		// 其余进入验签操作
		return false;
	}

	@Override
	protected boolean onAccessDenied(ServletRequest request1, ServletResponse response1) throws Exception{
		try{
			HttpServletRequest request = (HttpServletRequest)request1;
			HttpServletResponse response = (HttpServletResponse)response1;
			// 从 http 请求头中取出
			String token = request.getHeader("token");
			// 执行认证
			if(token == null || "".equals(token)){
				throw new JwtException("jwt.not.token", null);
			}
			JwtToken jwtToken = new JwtToken(token);
			// 提交给realm进行登入，如果错误他会抛出异常并被捕获
			Subject subject = SecurityUtils.getSubject();
			try{
				subject.login(jwtToken);
				onLoginSuccess(jwtToken, subject, request1, response1);
			}catch(Exception e){
				throw new JwtException("jwt.sign.invalid", null);
			}
			return true;

		}catch(Exception e){
			log.info(e.getMessage(), e);
			return onLoginFailure(request1, response1, e.getMessage());
		}
	}

	/**
	 * 处理异常
	 * 
	 * @param request
	 * @param response
	 * @param message
	 * @return
	 * @throws IOException
	 */
	private boolean onLoginFailure(ServletRequest request, ServletResponse response, String message) throws IOException{
		HttpServletResponse res = (HttpServletResponse)response;
		AjaxResult ajaxResult = AjaxResult.error(message);
		ServletUtils.renderString(res, objectMapper.writeValueAsString(ajaxResult));
		return false;
	}

	/**
	 * Shiro 利用 JWT token 登录成功，会进入该方法
	 */
	private boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception{
		HttpServletResponse httpResponse = WebUtils.toHttp(response);
		String newToken = null;
		if(token instanceof JwtToken){
			newToken = JwtUtil.refreshTokenExpired(token.getCredentials().toString(), JwtUtil.SECRET);
		}
		if(newToken != null)
			httpResponse.setHeader(JwtUtil.AUTH_HEADER, newToken);
		return true;
	}
}
